/* This file includes helper functions/classes/etc to support callbacks from
 * C++ into Python */

#include <stdint.h>

// gContactAddedCallback

typedef bool (*_cffi_gContactAddedCallback_t)(intptr_t i_cp, intptr_t i_colObj0, int partId0, int index0, intptr_t i_colObj1, int partId1, int index1);

static _cffi_gContactAddedCallback_t _python_gContactAddedCallback = NULL;

static bool _gContactAddedCallback_thunk(btManifoldPoint& cp, const btCollisionObjectWrapper* colObj0, int partId0, int index0, const btCollisionObjectWrapper* colObj1, int partId1, int index1) {
    intptr_t i_cp = (intptr_t)&cp;
    intptr_t i_colObj0 = (intptr_t)colObj0;
    intptr_t i_colObj1 = (intptr_t)colObj1;
    return _python_gContactAddedCallback(i_cp, i_colObj0, partId0, index0, i_colObj1, partId1, index1);
}

static void _py_set_gContactAddedCallback(intptr_t pyptr) {
    if (pyptr) {
        _python_gContactAddedCallback = (_cffi_gContactAddedCallback_t)pyptr;
        gContactAddedCallback = &_gContactAddedCallback_thunk;
    } else {
        gContactAddedCallback = NULL;
    }
}

// gContactProcessedCallback

typedef bool (*_cffi_gContactProcessedCallback_t)(intptr_t cp, intptr_t body0, intptr_t body1);

static _cffi_gContactProcessedCallback_t _python_gContactProcessedCallback = NULL;

static bool _gContactProcessedCallback_thunk(btManifoldPoint& cp, void* body0,void* body1) {
    intptr_t i_cp = (intptr_t)&cp;
    intptr_t i_body0 = (intptr_t)body0;
    intptr_t i_body1 = (intptr_t)body1;
    return _python_gContactProcessedCallback(i_cp, i_body0, i_body1);
}

static void _py_set_gContactProcessedCallback(intptr_t pyptr) {
    if (pyptr) {
        _python_gContactProcessedCallback = (_cffi_gContactProcessedCallback_t)pyptr;
        gContactProcessedCallback = &_gContactProcessedCallback_thunk;
    } else {
        gContactProcessedCallback = NULL;
    }
}

// gContactDestroyedCallback

typedef bool (*_cffi_gContactDestroyedCallback_t)(intptr_t userPersistentData);

static _cffi_gContactDestroyedCallback_t _python_gContactDestroyedCallback = NULL;

static bool _gContactDestroyedCallback_thunk(void* userPersistentData) {
    intptr_t i_userPersistentData = (intptr_t)userPersistentData;
    return _python_gContactDestroyedCallback(i_userPersistentData);
}

static void _py_set_gContactDestroyedCallback(intptr_t pyptr) {
    if (pyptr) {
        _python_gContactDestroyedCallback = (_cffi_gContactDestroyedCallback_t)pyptr;
        gContactDestroyedCallback = &_gContactDestroyedCallback_thunk;
    } else {
        gContactDestroyedCallback = NULL;
    }
}

// btTriangleCallback

typedef void (_cffi_btTriangleCallback_processTriangle_t)(intptr_t, intptr_t, intptr_t, int, int);

class _py_btTriangleCallback : public btTriangleCallback {
public:
    intptr_t _processTriangle_ptr;

    virtual void processTriangle(btVector3 *triangle, int partId, int triangleIndex) {
        _cffi_btTriangleCallback_processTriangle_t *cb = (_cffi_btTriangleCallback_processTriangle_t *)_processTriangle_ptr;
        cb((intptr_t)triangle, (intptr_t)(triangle+1), (intptr_t)(triangle+2), partId, triangleIndex);
    }
};

// btInternalTriangleIndexCallback

typedef void (_cffi_btInternalTriangleIndexCallback_internalProcessTriangleIndex_t)(intptr_t, intptr_t, intptr_t, int, int);

class _py_btInternalTriangleIndexCallback : public btInternalTriangleIndexCallback {
public:
    intptr_t _internalProcessTriangleIndex_ptr;

    virtual void internalProcessTriangleIndex(btVector3 *triangle, int partId, int triangleIndex) {
        _cffi_btInternalTriangleIndexCallback_internalProcessTriangleIndex_t *cb = (_cffi_btInternalTriangleIndexCallback_internalProcessTriangleIndex_t *)_internalProcessTriangleIndex_ptr;
        cb((intptr_t)triangle, (intptr_t)(triangle+1), (intptr_t)(triangle+2), partId, triangleIndex);
    }
};

// btIDebugDraw

typedef void (_cffi_btIDebugDraw_drawLine_t)(intptr_t, intptr_t, intptr_t, intptr_t);
typedef void (_cffi_btIDebugDraw_drawSphere_t_t)(btScalar, intptr_t, intptr_t);
typedef void (_cffi_btIDebugDraw_drawSphere_p_t)(intptr_t, btScalar, intptr_t);
typedef void (_cffi_btIDebugDraw_drawTriangle_t)(intptr_t, intptr_t, intptr_t, intptr_t, intptr_t, intptr_t, intptr_t, btScalar);
typedef void (_cffi_btIDebugDraw_drawContactPoint_t)(intptr_t, intptr_t, btScalar, int, intptr_t);
typedef void (_cffi_btIDebugDraw_reportErrorWarning_t)(const char *);
typedef void (_cffi_btIDebugDraw_draw3dText_t)(intptr_t, const char *);
typedef void (_cffi_btIDebugDraw_setDebugMode_t)(int);
typedef int (_cffi_btIDebugDraw_getDebugMode_t)(void);
typedef void (_cffi_btIDebugDraw_drawAabb_t)(intptr_t, intptr_t, intptr_t);
typedef void (_cffi_btIDebugDraw_drawTransform_t)(intptr_t, btScalar);
typedef void (_cffi_btIDebugDraw_drawArc_t)(intptr_t, intptr_t, intptr_t, btScalar, btScalar, btScalar, btScalar, intptr_t, bool, btScalar);
typedef void (_cffi_btIDebugDraw_drawSpherePatch_t)(intptr_t, intptr_t, intptr_t, btScalar, btScalar, btScalar, btScalar, btScalar, intptr_t, btScalar);
typedef void (_cffi_btIDebugDraw_drawBox_t)(intptr_t, intptr_t, intptr_t, intptr_t);
typedef void (_cffi_btIDebugDraw_drawCapsule_t)(btScalar, btScalar, int, intptr_t, intptr_t);
typedef void (_cffi_btIDebugDraw_drawCylinder_t)(btScalar, btScalar, int, intptr_t, intptr_t);
typedef void (_cffi_btIDebugDraw_drawCone_t)(btScalar, btScalar, int, intptr_t, intptr_t);
typedef void (_cffi_btIDebugDraw_drawPlane_t)(intptr_t, btScalar, intptr_t, intptr_t);

class _py_btIDebugDraw : public btIDebugDraw {
public:
    // Pointers to the corresponding Python functions (set up from python
    // during __new__):
    intptr_t _drawLine_ptr;
    intptr_t _drawSphere_t_ptr;
    intptr_t _drawSphere_p_ptr;
    intptr_t _drawTriangle_ptr;
    intptr_t _drawContactPoint_ptr;
    intptr_t _reportErrorWarning_ptr;
    intptr_t _draw3dText_ptr;
    intptr_t _setDebugMode_ptr;
    intptr_t _getDebugMode_ptr;
    intptr_t _drawAabb_ptr;
    intptr_t _drawTransform_ptr;
    intptr_t _drawArc_ptr;
    intptr_t _drawSpherePatch_ptr;
    intptr_t _drawBox_ptr;
    intptr_t _drawCapsule_ptr;
    intptr_t _drawCylinder_ptr;
    intptr_t _drawCone_ptr;
    intptr_t _drawPlane_ptr;

    // The following are wrappers to allow calling back from Python into C++ to
    // invoke the default (inherited but overridden) methods for these routines
    // on the base class:

    // (drawLine() is not implemented in the base class)

    void _drawSphere_t(btScalar radius, const btTransform& transform, const btVector3& color) {
        btIDebugDraw::drawSphere(radius, transform, color);
    }

    void _drawSphere_p(const btVector3& p, btScalar radius, const btVector3& color) {
        btIDebugDraw::drawSphere(p, radius, color);
    }

    // (drawContactPoint() is not implemented in the base class)
    // (reportErrorWarning() is not implemented in the base class)
    // (draw3dText() is not implemented in the base class)
    // (setDebugMode() is not implemented in the base class)
    // (getDebugMode() is not implemented in the base class)

    void _drawTriangle(const btVector3& v0, const btVector3& v1, const btVector3& v2, const btVector3& n0, const btVector3& n1, const btVector3& n2, const btVector3& color, btScalar alpha) {
        btIDebugDraw::drawTriangle(v0, v1, v2, n0, n1, n2, color, alpha);
    }

    void _drawTriangle(const btVector3& v0, const btVector3& v1, const btVector3& v2, const btVector3& color, btScalar alpha) {
        btIDebugDraw::drawTriangle(v0, v1, v2, color, alpha);
    }

    void _drawAabb(const btVector3& from, const btVector3& to, const btVector3& color) {
        btIDebugDraw::drawAabb(from, to, color);
    }

    void _drawTransform(const btTransform& transform, btScalar orthoLen) {
        btIDebugDraw::drawTransform(transform, orthoLen);
    }

    void _drawArc(const btVector3& center, const btVector3& normal, const btVector3& axis, btScalar radiusA, btScalar radiusB, btScalar minAngle, btScalar maxAngle, const btVector3& color, bool drawSect, btScalar stepDegrees) {
        btIDebugDraw::drawArc(center, normal, axis, radiusA, radiusB, minAngle, maxAngle, color, drawSect, stepDegrees);
    }

    void _drawSpherePatch(const btVector3& center, const btVector3& up, const btVector3& axis, btScalar radius, btScalar minTh, btScalar maxTh, btScalar minPs, btScalar maxPs, const btVector3& color, btScalar stepDegrees) {
        btIDebugDraw::drawSpherePatch(center, up, axis, radius, minTh, maxTh, minPs, maxPs, color, stepDegrees);
    }

    void _drawBox(const btVector3& bbMin, const btVector3& bbMax, const btVector3& color) {
        btIDebugDraw::drawBox(bbMin, bbMax, color);
    }
    // NOTE: These arguments are reversed from the original, to make calling
    // back and forth to Python a bit less stupid..
    void _drawBox(const btVector3& bbMin, const btVector3& bbMax, btVector3& color, const btTransform& trans) {
        btIDebugDraw::drawBox(bbMin, bbMax, trans, color);
    }

    void _drawCapsule(btScalar radius, btScalar halfHeight, int upAxis, const btTransform& transform, const btVector3& color) {
        btIDebugDraw::drawCapsule(radius, halfHeight, upAxis, transform, color);
    }

    void _drawCylinder(btScalar radius, btScalar halfHeight, int upAxis, const btTransform& transform, const btVector3& color) {
        btIDebugDraw::drawCylinder(radius, halfHeight, upAxis, transform, color);
    }

    void _drawCone(btScalar radius, btScalar height, int upAxis, const btTransform& transform, const btVector3& color) {
        btIDebugDraw::drawCone(radius, height, upAxis, transform, color);
    }

    void _drawPlane(const btVector3& planeNormal, btScalar planeConst, const btTransform& transform, const btVector3& color) {
        btIDebugDraw::drawPlane(planeNormal, planeConst, transform, color);
    }

    // Callback functions to invoke the corresponding python methods when
    // called from C++:

    virtual void drawLine(const btVector3& from,const btVector3& to,const btVector3& color) {
        _cffi_btIDebugDraw_drawLine_t *cb = (_cffi_btIDebugDraw_drawLine_t *)_drawLine_ptr;
        cb((intptr_t)&from, (intptr_t)&to, (intptr_t)&color, 0);
    }

    virtual void drawLine(const btVector3& from,const btVector3& to, const btVector3& fromColor, const btVector3& toColor) {
        _cffi_btIDebugDraw_drawLine_t *cb = (_cffi_btIDebugDraw_drawLine_t *)_drawLine_ptr;
        cb((intptr_t)&from, (intptr_t)&to, (intptr_t)&fromColor, (intptr_t)&toColor);
    }

    virtual void drawSphere(btScalar radius, const btTransform& transform, const btVector3& color) {
        _cffi_btIDebugDraw_drawSphere_t_t *cb = (_cffi_btIDebugDraw_drawSphere_t_t *)_drawSphere_t_ptr;
        cb(radius, (intptr_t)&transform, (intptr_t)&color);
    }

    virtual void drawSphere(const btVector3& p, btScalar radius, const btVector3& color) {
        _cffi_btIDebugDraw_drawSphere_p_t *cb = (_cffi_btIDebugDraw_drawSphere_p_t *)_drawSphere_p_ptr;
        cb((intptr_t)&p, radius, (intptr_t)&color);
    }

    virtual void drawTriangle(const btVector3& v0, const btVector3& v1, const btVector3& v2, const btVector3& n0, const btVector3& n1, const btVector3& n2, const btVector3& color, btScalar alpha) {
        _cffi_btIDebugDraw_drawTriangle_t *cb = (_cffi_btIDebugDraw_drawTriangle_t *)_drawTriangle_ptr;
        cb((intptr_t)&v0, (intptr_t)&v1, (intptr_t)&v2, (intptr_t)&n0, (intptr_t)&n1, (intptr_t)&n2, (intptr_t)&color, alpha);
    }

    virtual void drawTriangle(const btVector3& v0, const btVector3& v1, const btVector3& v2, const btVector3& color, btScalar alpha) {
        _cffi_btIDebugDraw_drawTriangle_t *cb = (_cffi_btIDebugDraw_drawTriangle_t *)_drawTriangle_ptr;
        cb((intptr_t)&v0, (intptr_t)&v1, (intptr_t)&v2, 0, 0, 0, (intptr_t)&color, alpha);
    }

    virtual void drawContactPoint(const btVector3& pointOnB, const btVector3& normalOnB, btScalar distance, int lifetime, const btVector3& color) {
        _cffi_btIDebugDraw_drawContactPoint_t *cb = (_cffi_btIDebugDraw_drawContactPoint_t *)_drawContactPoint_ptr;
        cb((intptr_t)&pointOnB, (intptr_t)&normalOnB, distance, lifetime, (intptr_t)&color);
    }

    virtual void reportErrorWarning(const char* warningString) {
        _cffi_btIDebugDraw_reportErrorWarning_t *cb = (_cffi_btIDebugDraw_reportErrorWarning_t *)_reportErrorWarning_ptr;
        cb(warningString);
    }

    virtual void draw3dText(const btVector3& location, const char* textString) {
        _cffi_btIDebugDraw_draw3dText_t *cb = (_cffi_btIDebugDraw_draw3dText_t *)_draw3dText_ptr;
        cb((intptr_t)&location, textString);
    }

    virtual void setDebugMode(int debugMode) {
        _cffi_btIDebugDraw_setDebugMode_t *cb = (_cffi_btIDebugDraw_setDebugMode_t *)_setDebugMode_ptr;
        cb(debugMode);
    }

    virtual int getDebugMode() const {
        _cffi_btIDebugDraw_getDebugMode_t *cb = (_cffi_btIDebugDraw_getDebugMode_t *)_getDebugMode_ptr;
        return cb();
    }

    virtual void drawAabb(const btVector3& from, const btVector3& to, const btVector3& color) {
        _cffi_btIDebugDraw_drawAabb_t *cb = (_cffi_btIDebugDraw_drawAabb_t *)_drawAabb_ptr;
        cb((intptr_t)&from, (intptr_t)&to, (intptr_t)&color);
    }

    virtual void drawTransform(const btTransform& transform, btScalar orthoLen) {
        _cffi_btIDebugDraw_drawTransform_t *cb = (_cffi_btIDebugDraw_drawTransform_t *)_drawTransform_ptr;
        cb((intptr_t)&transform, orthoLen);
    }

    virtual void drawArc(const btVector3& center, const btVector3& normal, const btVector3& axis, btScalar radiusA, btScalar radiusB, btScalar minAngle, btScalar maxAngle, const btVector3& color, bool drawSect, btScalar stepDegrees = btScalar(10.f)) {
        _cffi_btIDebugDraw_drawArc_t *cb = (_cffi_btIDebugDraw_drawArc_t *)_drawArc_ptr;
        cb((intptr_t)&center, (intptr_t)&normal, (intptr_t)&axis, radiusA, radiusB, minAngle, maxAngle, (intptr_t)&color, drawSect, stepDegrees);
    }

    virtual void drawSpherePatch(const btVector3& center, const btVector3& up, const btVector3& axis, btScalar radius, btScalar minTh, btScalar maxTh, btScalar minPs, btScalar maxPs, const btVector3& color, btScalar stepDegrees = btScalar(10.f)) {
        _cffi_btIDebugDraw_drawSpherePatch_t *cb = (_cffi_btIDebugDraw_drawSpherePatch_t *)_drawSpherePatch_ptr;
        cb((intptr_t)&center, (intptr_t)&up, (intptr_t)&axis, radius, minTh, maxTh, minPs, maxPs, (intptr_t)&color, stepDegrees);
    }

    virtual void drawBox(const btVector3& bbMin, const btVector3& bbMax, const btVector3& color) {
        _cffi_btIDebugDraw_drawBox_t *cb = (_cffi_btIDebugDraw_drawBox_t *)_drawBox_ptr;
        cb((intptr_t)&bbMin, (intptr_t)&bbMax, (intptr_t)&color, 0);
    }

    virtual void drawBox(const btVector3& bbMin, const btVector3& bbMax, const btTransform& trans, const btVector3& color) {
        _cffi_btIDebugDraw_drawBox_t *cb = (_cffi_btIDebugDraw_drawBox_t *)_drawBox_ptr;
        cb((intptr_t)&bbMin, (intptr_t)&bbMax, (intptr_t)&color, (intptr_t)&trans);
    }

    virtual void drawCapsule(btScalar radius, btScalar halfHeight, int upAxis, const btTransform& transform, const btVector3& color) {
        _cffi_btIDebugDraw_drawCapsule_t *cb = (_cffi_btIDebugDraw_drawCapsule_t *)_drawCapsule_ptr;
        cb(radius, halfHeight, upAxis, (intptr_t)&transform, (intptr_t)&color);
    }

    virtual void drawCylinder(btScalar radius, btScalar halfHeight, int upAxis, const btTransform& transform, const btVector3& color) {
        _cffi_btIDebugDraw_drawCylinder_t *cb = (_cffi_btIDebugDraw_drawCylinder_t *)_drawCylinder_ptr;
        cb(radius, halfHeight, upAxis, (intptr_t)&transform, (intptr_t)&color);
    }

    virtual void drawCone(btScalar radius, btScalar height, int upAxis, const btTransform& transform, const btVector3& color) {
        _cffi_btIDebugDraw_drawCone_t *cb = (_cffi_btIDebugDraw_drawCone_t *)_drawCone_ptr;
        cb(radius, height, upAxis, (intptr_t)&transform, (intptr_t)&color);
    }

    virtual void drawPlane(const btVector3& planeNormal, btScalar planeConst, const btTransform& transform, const btVector3& color) {
        _cffi_btIDebugDraw_drawPlane_t *cb = (_cffi_btIDebugDraw_drawPlane_t *)_drawPlane_ptr;
        cb((intptr_t)&planeNormal, planeConst, (intptr_t)&transform, (intptr_t)&color);
    }

};

